import logging

from os import path
from textwrap import fill

import svgwrite

from graphviz import Digraph
from lakesuperior import env
env.setup()
from lakesuperior.api import resource as rsrc_api
from lakesuperior.dictionaries.namespaces import ns_collection as nsc
from lakesuperior.exceptions import ResourceNotExistsError

import grayspread
from grayspread.helpers import get_lii_type
from grayspread.store import txn_ctx

logger = logging.getLogger(__name__)

CVS_SIZE = 128      # Max canvas bounding box (width & height)
BORDER = 4  # Border around the drawing (to allow painting strokes correctly)
D_SCALE_X = 1 / 2   # Scaling of depth dimension on X axis.
D_SCALE_Y = 1 / 4   # Scaling of depth dimension on Y axis.

DWG_AREA = CVS_SIZE - BORDER * 2
CTR = CVS_SIZE / 2

FONT_PATH = path.join(grayspread.basedir, 'static', 'fonts')

display_rels = {
    nsc['dcterms'].contributor: 'Contributor',
    nsc['dcterms'].spatial: 'Spatial\nContext',
    nsc['lii'].employed: 'Employed',
    nsc['lii'].engendered: 'Engendered',
    nsc['lii'].hasPreferredRepresentation: 'Has Preferred\nRepresentation',
    nsc['lii'].hasRepresentation: 'Has Representation',
    nsc['lii'].isMadeOf: 'Has Significant\nMaterial',
    nsc['lii'].produced: 'Produced',
}

node_styles = {
    nsc['lii'].Artifact: 'box',
    nsc['lii'].Agent: 'parallelogram',
    nsc['lii'].Action: 'hexagon',
    nsc['lii'].Document: 'note',
    nsc['lii'].Idea: 'ellipse',
    nsc['lii'].Material: 'octagon',
    nsc['lii'].Place: 'trapezium',
}


def draw_dimensions(w=0, h=0, d=0, di=0):
    """
    Draw dimensional wireframe for 2d/3d objects.
    """
    if w:
        if h:
            if d:
                return draw_box(w, h, d)
            else:
                return draw_rect(w, h)
        elif di:
            return draw_h_cylinder(w, di)
    elif di:
        if h:
            return draw_v_cylinder(di, h)
        else:
            return draw_sphere()


params = {'stroke': 'black', 'stroke_width': 1, 'fill': 'lightgrey'}
nofill_params = dict(params, fill='none')
nostroke_params = dict(params, stroke='none')


def draw_rect(w, h):
    scale = DWG_AREA / max(w, h)
    offset_x = BORDER + (DWG_AREA - w * scale) / 2
    offset_y = BORDER + (DWG_AREA - h * scale) / 2
    print(f'Scale: {scale}')
    print(f'Offset x: {offset_x}; offset y: {offset_y}')
    print(f'w: {w * scale}; h: {h * scale}')
    dwg = svgwrite.Drawing(
            'rect.svg', size=(CVS_SIZE, CVS_SIZE), profile='tiny')
    dwg.add(dwg.rect((offset_x, offset_y), (w * scale, h * scale), **params))

    #dwg.save()
    return dwg


def draw_box(w, h, d):
    """
    Isometric box, h is 1/2, v is 1/4.

    Points a1, b1, c1, d1 are the front face, starting from top left
    corner, going clockwise.

    Points a2, b2, c2, d2 are the back face.
    """
    bbox_x = w + d * D_SCALE_X
    bbox_y = h + d * D_SCALE_Y
    print(f'bbox_x: {bbox_x}; bbox_y: {bbox_y}')
    scale = DWG_AREA / max(bbox_x, bbox_y)
    print(f'Scale: {scale}')
    offset_x = BORDER + (DWG_AREA - bbox_x * scale) / 2
    offset_y = BORDER + (DWG_AREA - bbox_y * scale) / 2
    print(f'offset_x: {offset_x}; offset_y: {offset_y}')

    a1 = (offset_x, offset_y)
    b1 = (offset_x + w * scale, offset_y)
    c1 = (offset_x + w * scale, offset_y + h * scale)
    d1 = (offset_x, offset_y + h * scale)
    a2 = (a1[0] + d * D_SCALE_X * scale, a1[1] + d * D_SCALE_Y * scale)
    b2 = (b1[0] + d * D_SCALE_X * scale, b1[1] + d * D_SCALE_Y * scale)
    c2 = (c1[0] + d * D_SCALE_X * scale, c1[1] + d * D_SCALE_Y * scale)
    d2 = (d1[0] + d * D_SCALE_X * scale, d1[1] + d * D_SCALE_Y * scale)

    dwg = svgwrite.Drawing(
            'box.svg', size=(CVS_SIZE, CVS_SIZE), profile='tiny')
    grp = dwg.g()

    # Side face.
    grp.add(dwg.polygon((a1, d1, d2, a2), **params))
    # Top face.
    grp.add(dwg.polygon((a1, b1, b2, a2), **params))
    # Front face.
    grp.add(dwg.rect(a2, (w * scale, h * scale), **params))

    dwg.add(grp)
    # Mirror
    grp.scale(-1, 1)
    grp.translate((-CVS_SIZE, 0))

    #dwg.save()
    return dwg


def draw_v_cylinder(di, w):
    dwg = svgwrite.Drawing(
            'vcyl.svg', size=(CVS_SIZE, CVS_SIZE), profile='tiny')

    grp = _draw_v_cylinder_grp(di, w)
    dwg.add(grp)

    #dwg.save()
    return dwg


def draw_h_cylinder(h, di):
    dwg = svgwrite.Drawing(
            'hcyl.svg', size=(CVS_SIZE, CVS_SIZE), profile='tiny')
    grp = _draw_v_cylinder_grp(di, h)
    grp.rotate(90, center=(CTR, CTR))
    dwg.add(grp)

    #dwg.save()
    return dwg


def _draw_v_cylinder_grp(di, h):
    bbox_h = di * D_SCALE_Y + h
    scale = DWG_AREA / max(di, bbox_h)
    print(f'Scale: {scale}')

    rx = di / 2 * scale
    ry = di * D_SCALE_Y / 2 * scale
    c1 = (BORDER + DWG_AREA / 2, BORDER + (DWG_AREA - h * scale) / 2)
    c2 = (c1[0], CVS_SIZE - c1[1])
    a1 = (c1[0] - rx, c1[1])
    b1 = (a1[0], c2[1])
    a2 = (c1[0] + rx, a1[1])
    b2 = (a2[0], b1[1])
    wh = (rx * 2, c2[1] - c1[1])

    grp = svgwrite.container.Group()

    # Bottom cap
    grp.add(svgwrite.shapes.Ellipse(c2, (rx, ry), **params))
    # Filled rect to hide half bottom cap
    grp.add(svgwrite.shapes.Rect(a1, wh, **nostroke_params))
    # Top cap
    grp.add(svgwrite.shapes.Ellipse(c1, (rx, ry), **params))
    grp.add(svgwrite.shapes.Line(a1, b1, **params))
    grp.add(svgwrite.shapes.Line(a2, b2, **params))

    return grp


def draw_circle():
    c1 = (CTR, CTR)
    r = DWG_AREA / 2

    dwg = svgwrite.Drawing(
            'circle.svg', size=(CVS_SIZE, CVS_SIZE), profile='tiny')

    dwg.add(dwg.circle(c1, r, **params))

    #dwg.save()
    return dwg


def draw_sphere():
    c1 = (CTR, CTR)
    r = DWG_AREA / 2

    dwg = svgwrite.Drawing(
            'sphere.svg', size=(CVS_SIZE, CVS_SIZE), profile='tiny')

    dwg.add(dwg.circle(c1, r, **params))
    path = dwg.path(d=('M', c1[0] - r, c1[1]), **nofill_params)
    path.push_arc(
            (c1[0] + r, c1[1]), 0, (r, r / 4), angle_dir='-', absolute=True)
    path.push('M', (c1[0], c1[1] - r))
    path.push_arc(
            (c1[0], c1[1] + r), 0, (r / 2, r), angle_dir='-', absolute=True)

    dwg.add(path)

    #dwg.save()
    return dwg


def draw_neighbor_graph(rsrc):
    """ Draw the immediate neighborhood graph of a resource. """
    graph_attrs = {
        #'size': '2,1',
        #'dpi': '320',
        'ratio': 'scale',
        'sep': '+20',
        'overlap': 'false',
        'rankdir': 'LR',
        'bgcolor': 'transparent',
    }
    node_attrs = {
        'style': 'filled',
        'fillcolor': '#888888',
        'fontpath': FONT_PATH,
        'fontsize': '10',
        'fontname': 'Liberation Mono',
    }
    edge_attrs = {
        'fontpath': FONT_PATH,
        'fontsize': '9',
        'fontname': 'helvetica',
    }
    g = Digraph(
            f'Neighborhood of {rsrc.uid}', format='svg', engine='dot',
            graph_attr=graph_attrs, node_attr=node_attrs, edge_attr=edge_attrs)

    with txn_ctx():
        # Current resource.
        g.node(
                rsrc.uid, shape=node_styles[get_lii_type(rsrc)],
                fillcolor='#cccccc',
                label=fill(rsrc.imr.value(nsc['skos'].prefLabel), width=24))
        # Outbound links.
        for p, o in rsrc.imr[rsrc.uri::]:
            if p in display_rels:
                uid = str(o).replace(nsc['fcres'], '')
                try:
                    t_rsrc = rsrc_api.get(uid)
                except ResourceNotExistsError:
                    pass
                else:
                    g.node(
                            t_rsrc.uid,
                            label=fill(
                                    t_rsrc.imr.value(nsc['skos'].prefLabel),
                                    width=24),
                            shape=node_styles[get_lii_type(t_rsrc)],
                            URL=f'/view{t_rsrc.uid}')
                    g.edge(rsrc.uid, t_rsrc.uid, label=display_rels[p])
        # Inbound links.
        for s, p in rsrc.imr[::rsrc.uri]:
            if p in display_rels:
                uid = str(s).replace(nsc['fcres'], '')
                try:
                    s_rsrc = rsrc_api.get(uid)
                except ResourceNotExistsError:
                    pass
                else:
                    g.node(
                            s_rsrc.uid,
                            label=fill(
                                    s_rsrc.imr.value(nsc['skos'].prefLabel),
                                    width=24),
                            shape=node_styles[get_lii_type(s_rsrc)],
                            URL=f'/view{s_rsrc.uid}')
                    g.edge(s_rsrc.uid, rsrc.uid, label=display_rels[p])

    return g.pipe().decode()
